/*
  Labotronik EthernetWifi Switch
  Copyright (C) 2012 Marc Chapdelaine

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "TftpClient.hpp"
#include <QUdpSocket>
#include <QString>
#include <QByteArray>
#include <QHostAddress>
#include <QTimer>
#include <QFile>
#include <QDataStream>
/*
#include <QDialog>
#include <QStatusBar>
#include <QVBoxLayout>
*/
#include <QtGui>

#include <iostream>
#include <iomanip>

using namespace Tftp;
using namespace std;

quint16 TftpClient::tidSeed = 20000;

TftpClient::TftpClient(const QString& serverAddress_, quint16 port_, QWidget* parent) :
	QDialog(parent),
	port(port_),
	serverTid(0),
	currentblockId(0),
	readBufferSize(0),
	socket(NULL),
	operationTimer(NULL),
	currentState(INITSTATE),
	file(NULL),
	fileData(NULL),
	readBuffer(NULL),
	statusBar(NULL),
	mainLayout(NULL) {
	serverAddress = new QString(serverAddress_);
	operationTimer = new QTimer(this);
	readBuffer = new char[512];
	
	statusBar = new QStatusBar(this);
	statusBar->showMessage("Hellow");
	
	resize(400, 200);
	mainLayout = new QVBoxLayout;
	
	mainLayout->addWidget(statusBar);
	
	setLayout(mainLayout);
	
	QString windowTitle = QString(tr("Tftp Client")) + QString::number(SWITCHER_VERSION);
	setWindowTitle(windowTitle);
}

TftpClient::~TftpClient() {
	if(serverAddress != NULL)
		delete serverAddress;
	if(socket)
		delete socket;
	if(operationTimer)
		delete operationTimer;
	if(file)
		delete file;
	if(fileData)
		delete fileData;
	if(readBuffer)
		delete readBuffer;
	if(statusBar)
		delete statusBar;
	if(mainLayout)
		delete mainLayout;
}

bool TftpClient::put(const QString& remoteFileName, const QString& localFileName) {
	if(currentState != INITSTATE)
		currentState = INITSTATE;
		
	cout << "PUT\n";

	file = new QFile(localFileName);
	if(!file->open(QIODevice::ReadOnly))
		return false;
	fileData = new QDataStream(file);
	
	cout << "Debug: File is opened\n";
		
	socket = new QUdpSocket(this);
	socket->bind(QHostAddress(*serverAddress), generateTid());
	
	cout << "Debug: socket bind\n";
	
	connect(socket, SIGNAL(readyRead()), this, SLOT(readDatagram()));
	connect(operationTimer, SIGNAL(timeout()), this, SLOT(operationTimeout()));

	cout << "Debug: Sending request\n";
	sendRequestPacket(WRITEREQUEST, remoteFileName);
	cout << "Debug: Request sent\n";
	return true;
}

bool TftpClient::get(const QString& remoteFileName, const QString& localFileName) {

	return true;
}

bool TftpClient::sendRequestPacket(Opcode code, const QString& remoteFileName) {
	cout << "Debug: sendRequestPacket\n";
	if((code != READREQUEST) &&	(code != WRITEREQUEST))
		return false;

	cout << "Debug: creating packet, remote file name : " << remoteFileName.toStdString() << "\n";
	QByteArray packet;
	packet.append(static_cast<char>(code >> 8));
	packet.append(static_cast<char>(code));
	packet.append(remoteFileName);
	packet.append(static_cast<char>(0x00));
	packet.append(QString("octet"));
	packet.append(static_cast<char>(0x00));
	
	if(socket == NULL)
		return false;
	
	cout << "Debug: starting timer\n";
	operationTimer->start(1000);
	
	displayQByteArrayData(packet);
	
	qint64 numberSentBytes = socket->writeDatagram(packet, QHostAddress(*serverAddress), port);
	cout << "Debug: number of sent bytes: " << numberSentBytes << endl;
	
	
	currentState = CONNECTIONINITSTATE;
	
	return true;
}

bool TftpClient::sendDataPacket(bool increment) {
	cout << "Inside sendDataPacket\n";
	if(currentState != READYSENDFILESTATE)
		return false;
	else
		cout << "Error: no ready send file\n";
	
	cout << "SendDataPacket increment "; 
	if(increment) {
		readBufferSize = fileData->readRawData(readBuffer, 512);
		currentblockId++;
		cout << " yes, and readBufferSize is " << readBufferSize << "\n";
	} else
		cout << " no\n";
	
	QByteArray packet;
	//packet.resize(2 + 2 + readBufferSize);
	packet.append(static_cast<char>(DATA >> 8));
	packet.append(static_cast<char>(DATA));
	packet.append(static_cast<char>(currentblockId >> 8));
	packet.append(static_cast<char>(currentblockId));
	packet.append(readBuffer, readBufferSize);
	cout << "the packet size is : " << packet.size() << endl; 
	
	displayQByteArrayData(packet, 10);
	
	if(readBufferSize == 512)
		currentState = WAITINGDATAACKSTATE;
	else
		currentState = LASTDATAPACKETACKSTATE;
	
	operationTimer->start(1000);
	
	cout << "Sending the data packet on the port " << serverTid << endl;
	socket->writeDatagram(packet, QHostAddress(*serverAddress), serverTid);
}

void TftpClient::operationTimeout() {
	operationTimer->stop();
	cout << "Debug: Got timeout\n";
	statusBar->showMessage("Timeout!");
	switch(currentState) {

	case CONNECTIONINITSTATE :
		cout << "ConnectionInitState timeout\n";
		break;
	case WAITINGDATAACKSTATE :
		cout << "WaitingDataAckstate timeout\n";
		break;
	case LASTDATAPACKETACKSTATE :
		cout << "LastDataPacketState\n";
		break;
	
	case INITSTATE:
	case DATASENTSTATE :	
	case READYSENDFILESTATE :
	default:
		cout << "No timeout should occur in this state\n";
	}
	currentState = INITSTATE;
}

void TftpClient::readDatagram() {
	cout << "Received a packet!\n";
	QHostAddress remoteAddress;
	quint16 remotePort;
	while(socket->hasPendingDatagrams()) {
		QByteArray datagram;
		datagram.resize(socket->pendingDatagramSize());
		socket->readDatagram(datagram.data(), datagram.size(), &remoteAddress, &remotePort);
		
		operationTimer->stop();
		
		switch(currentState) {
		case CONNECTIONINITSTATE : {
			quint16 blockId;
			cout << "Got a packet in connectioninitstate\n";
			if(isAck(datagram, blockId) == true) {
				// Check if the block ID is 0
				if(blockId != 0)
					return;
				cout << "CONNECTIONINITSTATE Ack received, remote port is " << remotePort << endl;
				serverTid = remotePort;
				currentState = READYSENDFILESTATE;
				sendDataPacket(true);
			} else {
				currentState = INITSTATE;
			}
			break;
		}
		
		case WAITINGDATAACKSTATE : {
			if(remotePort != serverTid) {
				// Someone may corrupt the data ...
				cout << "Warning, wrong ACK TID, ignoring\n";
				return;
			}
			cout << "Inside WaitingDataAckState\n";
			quint16 blockId;
			if(isAck(datagram, blockId) == true) {
				if(blockId == currentblockId) {
					currentState = DATASENTSTATE;
					sendDataPacket(true);
				} else {
					currentState = DATASENTSTATE;
					sendDataPacket(false);
				}
			}
			break;
		}
			
	
		case LASTDATAPACKETACKSTATE : {
			if(remotePort != serverTid) {
				// Someone may corrupt the data ...
				cout << "Warning, wrong ACK TID, ignoring\n";
				return;
			}
			quint16 blockId;
			if(isAck(datagram, blockId) == true) {
				if(blockId == currentblockId) {
					// Hurray, the file was sent successfully !
					currentState = INITSTATE;
				} else {
					cout << "Got the wrong last packet block ID\n";
					currentState = INITSTATE;
				}
			}
			break;
		}
		
		
	
		case INITSTATE:
			cout << "Packet received in initstate, should not happens\n";
			break;
		case DATASENTSTATE :	
			cout << "Packet received in datasentstate, should not happens\n";
			break;
		case READYSENDFILESTATE :
			cout << "Packet received in readysendfile, should not happens\n";
			break;
		default:
			cout << "This case should not happens\n";
		}
	}
}

Opcode TftpClient::getOpcodeFromPacket(const QByteArray& packet) {
	cout << "Get opcode from packet display: \n";
	displayQByteArrayData(packet);
	cout << "The converted value is 0x" << setbase(16) << (packet[1] + (packet[0] << 8)) << setbase(10) << endl;
	if(packet.size() < 2)
		return UNKONWNOPCODE;
	else
		return static_cast<Opcode>((packet[1] + (packet[0] << 8)));
}

 bool TftpClient::isAck(const QByteArray& packet, quint16& blockNumber) {
	cout << "Check if Ack packet .. size : " << packet.size() << endl;
	if(packet.size() != 4)
		return false;
 
	if(getOpcodeFromPacket(packet) == ACK) {
		blockNumber = packet[3] + (packet[2] << 8);
		cout << "Ack block number is " << blockNumber << endl;
		return true;
	} else
		return false;
 }

unsigned short TftpClient::generateTid() {
	// TODO: maybe, in the future, do something more intelligent
	if(tidSeed < 65535)
		tidSeed = 20000;
	
	return ++tidSeed;
}

void TftpClient::displayQByteArrayData(const QByteArray& data, unsigned int maxDisplay) {
	cout << "Display QbyteArray\n";
	unsigned int bytesToDisplay = data.size();
	if(bytesToDisplay > maxDisplay)
		bytesToDisplay = maxDisplay;
	
	for(unsigned int position = 0; position < bytesToDisplay; position++) {
		cout << "data[" << position << "] = 0x" << setbase(16) << static_cast<unsigned int>(data[position]) << " " << data[position] << setbase(10) << endl;
	}
}
